#include "hffplayer.h"

#include "confile.h"
#include "hlog.h"
#include "hstring.h"
#include "hscope.h"
#include "htime.h"
#include "HFFVideoPlayer.h"
#include "HFFAudioPlayer.h"

#define DEFAULT_BLOCK_TIMEOUT   10  // s

std::atomic_flag HFFPlayer::s_ffmpeg_init = ATOMIC_FLAG_INIT;//原子锁，初始化s_ffmpeg_init为clear状态

static double r2d(AVRational r)
{
	return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}

static void list_devices() {
    AVFormatContext* fmt_ctx = avformat_alloc_context();
    AVDictionary* options = NULL;
    av_dict_set(&options, "list_devices", "true", 0);
#ifdef _WIN32
    const char drive[] = "dshow";
#elif defined(__linux__)
    const char drive[] = "v4l2";
#else
    const char drive[] = "avfoundation";
#endif
    AVInputFormat* ifmt = av_find_input_format(drive);
    if (ifmt) {
        avformat_open_input(&fmt_ctx, "video=dummy", ifmt, &options);
    }
    avformat_close_input(&fmt_ctx);
    avformat_free_context(fmt_ctx);
    av_dict_free(&options);
}

// NOTE: avformat_open_input,av_read_frame block
static int interrupt_callback(void* opaque) {
    if (opaque == NULL) return 0;
    HFFPlayer* player = (HFFPlayer*)opaque;
    if (player->quit ||
        time(NULL) - player->block_starttime > player->block_timeout) {
        hlogi("interrupt quit=%d media.src=%s", player->quit, player->media.src.c_str());
        return 1;
    }
    return 0;
}

void call_push(void* hp, void* frame)
{
    HFFPlayer* player = (HFFPlayer*)hp;
    player->push_frame((HFrame*)frame);//hframe中存放的是data的数据，data是由sws_scale产生； push_frame()会对hframe的内存空间进行复制；渲染时使用并对内存释放
}

HFFPlayer::HFFPlayer()
: HVideoPlayer()
, HThread() {
    fmt_opts = NULL;
    codec_opts = NULL;
    fmt_ctx = NULL;
    videoPlayer = NULL;
    audioPlayer = NULL;

    block_starttime = time(NULL);
    block_timeout = DEFAULT_BLOCK_TIMEOUT;
    quit = 0;

    //使用原子锁，当s_ffmpeg_init为clear状态时，test_and_set返回false并置为set状态，当s_ffmpeg_init为set状态时返回true
    //保证ffmpeg上下文只被初始化一次
    if (!s_ffmpeg_init.test_and_set())
    {
        // av_register_all();
        // avcodec_register_all();
        avformat_network_init();
        avdevice_register_all();
        list_devices();
    }
}

HFFPlayer::~HFFPlayer() {
    close();
}


int HFFPlayer::start() {
    quit = 0;

    return HThread::start();
}


int HFFPlayer::pause() 
{
    if(videoPlayer) videoPlayer->Clear();
    audioPlayer->pause();
    frame_buf.clear();
    return HThread::pause();
}

int HFFPlayer::resume() 
{
    audioPlayer->resume();
    return HThread::resume();
}


int HFFPlayer::stop() {
    hlogi("HFFPlayer::stop()");
    quit = 1;
    if(videoPlayer != NULL)
    {
        videoPlayer->stop();
    }

    if(audioPlayer != NULL)
    {
        audioPlayer->stop();
    }
    return HThread::stop();
}


int HFFPlayer::open() {
    std::string ifile;

    AVInputFormat* ifmt = NULL;
    switch (media.type) {
    case MEDIA_TYPE_CAPTURE:
    {
        ifile = "video=";
        ifile += media.src;
#ifdef _WIN32
        const char drive[] = "dshow";
#elif defined(__linux__)
        const char drive[] = "v4l2";
#else
        const char drive[] = "avfoundation";
#endif
        ifmt = av_find_input_format(drive);
        if (ifmt == NULL) {
            hloge("Can not find dshow");
            return -5;
        }
    }
        break;
    case MEDIA_TYPE_FILE:
    case MEDIA_TYPE_NETWORK:
        ifile = media.src;
        break;
    default:
        return -10;
    }

    hlogi("ifile:%s", ifile.c_str());
    int ret = 0;
    fmt_ctx = avformat_alloc_context();
    if (fmt_ctx == NULL) {
        hloge("avformat_alloc_context");
        ret = -10;
        return ret;
    }
    defer (if (ret != 0 && fmt_ctx) {avformat_free_context(fmt_ctx); fmt_ctx = NULL;})

    if (media.type == MEDIA_TYPE_NETWORK) {
        if (strncmp(media.src.c_str(), "rtsp:", 5) == 0) {
            std::string str = g_confile->GetValue("rtsp_transport", "video");
            if (strcmp(str.c_str(), "tcp") == 0 ||
                strcmp(str.c_str(), "udp") == 0) {
                av_dict_set(&fmt_opts, "rtsp_transport", str.c_str(), 0);//设置传输协议
            }
        }
        av_dict_set(&fmt_opts, "stimeout", "5000000", 0);   // us
    }
    av_dict_set(&fmt_opts, "buffer_size", "2048000", 0);
    fmt_ctx->interrupt_callback.callback = interrupt_callback;//中断阻塞回调
    fmt_ctx->interrupt_callback.opaque = this;
    block_starttime = time(NULL);
    ret = avformat_open_input(&fmt_ctx, ifile.c_str(), ifmt, &fmt_opts);
    if (ret != 0) {
        hloge("Open input file[%s] failed: %d", ifile.c_str(), ret);
        return ret;
    }
    fmt_ctx->interrupt_callback.callback = NULL;
    defer (if (ret != 0 && fmt_ctx) {avformat_close_input(&fmt_ctx);})

    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret != 0) {
        hloge("Can not find stream: %d", ret);
        return ret;
    }
    hlogi("stream_num=%d", fmt_ctx->nb_streams);

    video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    subtitle_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0);
    hlogi("video_stream_index=%d", video_stream_index);
    hlogi("audio_stream_index=%d", audio_stream_index);
    hlogi("subtitle_stream_index=%d", subtitle_stream_index);

    hlogi("=======%d, audio info==========", audio_stream_index);
    if(audio_stream_index >= 0)
    {
        AVStream* audioStream = fmt_ctx->streams[audio_stream_index];
        
        hlogi("codec_id = %d,%s", audioStream->codecpar->codec_id, avcodec_get_name(audioStream->codecpar->codec_id));
        hlogi("fomat = %d", audioStream->codecpar->format);
        hlogi("sample_rate = %d", audioStream->codecpar->sample_rate);
        hlogi("channels = %d ", audioStream->codecpar->channels);   

        if(audioPlayer == NULL)
        {
            audioPlayer = new HFFAudioPlayer(audioStream->codecpar->sample_rate, audioStream->codecpar->channels);
        }
        AVCodecParameters *audio_codec_param = avcodec_parameters_alloc();
        avcodec_parameters_copy(audio_codec_param, audioStream->codecpar);
        audioPlayer->SetPara(audio_codec_param);
        audioPlayer->start(); 
        audioPlayer->set_time_base(audioStream->time_base);
    }

    if (video_stream_index < 0) {
        hloge("Can not find video stream.");
        ret = -20;
        return ret;
    }

    AVStream* video_stream = fmt_ctx->streams[video_stream_index];
    video_time_base_num = video_stream->time_base.num;
    video_time_base_den = video_stream->time_base.den;
    video_stream->discard = AVDISCARD_DEFAULT;//选择丢弃哪些包,解封装层
    {
        hlogi("========%d, video info========", video_stream_index);
        hlogi("codec_id = %d,%s", video_stream->codecpar->codec_id,avcodec_get_name(video_stream->codecpar->codec_id));
        hlogi("fomat = %d", video_stream->codecpar->format);
        hlogi("width = %d", video_stream->codecpar->width);
        hlogi("height = %d", video_stream->codecpar->height);
        hlogi("video fps = %lf", r2d(video_stream->avg_frame_rate));
    }
    AVCodecParameters *codec_param = avcodec_parameters_alloc();
    avcodec_parameters_copy(codec_param, video_stream->codecpar);
    hlogi("codec_id=%d:%s", codec_param->codec_id, avcodec_get_name(codec_param->codec_id));

    if(videoPlayer == NULL)
    {
        videoPlayer = new HFFVideoPlayer();
    }
    videoPlayer->SetPara(codec_param);
    videoPlayer->SetHP(this);
    videoPlayer->SetOnDecodeCallback(call_push);//设置解码完成的回调函数
    videoPlayer->set_time_base(video_stream->time_base);
    videoPlayer->start();

    // HVideoPlayer member vars
    if (video_stream->avg_frame_rate.num && video_stream->avg_frame_rate.den) {
        fps = video_stream->avg_frame_rate.num / video_stream->avg_frame_rate.den;
    }

    width = codec_param->width;
    height = codec_param->height;
    duration = 0;
    start_time = 0;
    eof = 0;
    error = 0;
    if (video_time_base_num && video_time_base_den) {
        if (video_stream->duration > 0) {
            duration = video_stream->duration / (double)video_time_base_den * video_time_base_num * 1000;
        }
        if (video_stream->start_time > 0) {
            start_time = video_stream->start_time / (double)video_time_base_den * video_time_base_num * 1000;
        }
    }
    hlogi("fps=%d duration=%lldms start_time=%lldms", fps, duration, start_time);

    HThread::setSleepPolicy(HThread::SLEEP_FOR, 1); 
    return ret;
}

int HFFPlayer::close() 
{
    if (fmt_opts) {
        av_dict_free(&fmt_opts);
        fmt_opts = NULL;
    }

    if (codec_opts) {
        av_dict_free(&codec_opts);
        codec_opts = NULL;
    }

    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx);
        avformat_free_context(fmt_ctx);
        fmt_ctx = NULL;
    }

    if(audioPlayer != NULL)
    {
        audioPlayer->stop();
        delete audioPlayer;
        audioPlayer = NULL;
    }

    if(videoPlayer != NULL)
    {
        videoPlayer->stop();
        delete videoPlayer;
        videoPlayer = NULL;
    }

    return 0;
}

int HFFPlayer::seek(int64_t ms) {
    if (fmt_ctx) {
        clear_frame_cache();
        hlogi("seek=>%lldms", ms);
        return av_seek_frame(fmt_ctx, video_stream_index,
                (start_time+ms)/1000/(double)video_time_base_num*video_time_base_den,
                AVSEEK_FLAG_BACKWARD);
    }
    return 0;
}

bool HFFPlayer::doPrepare() {
    int ret = open();
    if (ret != 0) {
        if (!quit) {
            error = ret;
            event_callback(HPLAYER_OPEN_FAILED);
        }
        return false;
    }
    else {
        event_callback(HPLAYER_OPENED);
    }
    return true;
}

bool HFFPlayer::doFinish() 
{
    int ret = close();
    event_callback(HPLAYER_CLOSED);
    return ret == 0;
}


void HFFPlayer::doTask() {
    // loop until get a video frame

    AVPacket* pkt = av_packet_alloc();
    fmt_ctx->interrupt_callback.callback = interrupt_callback;
    fmt_ctx->interrupt_callback.opaque = this;
    block_starttime = time(NULL);
    int ret = av_read_frame(fmt_ctx, pkt);
    fmt_ctx->interrupt_callback.callback = NULL;
    if (ret != 0)
    {
        hlogi("No frame: %d", ret);
        if (!quit) {
            if (ret == AVERROR_EOF || avio_feof(fmt_ctx->pb)) {
                eof = 1;
                event_callback(HPLAYER_EOF);
            }
            else {
                error = ret;
                event_callback(HPLAYER_ERROR);
            }
        }
        av_packet_free(&pkt);
        return;
    }

    if (pkt->stream_index == video_stream_index)
    {
        videoPlayer->Push(pkt);
    }
    else if(pkt->stream_index == audio_stream_index)
    {
        audioPlayer->Push(pkt);
    }
    else
    {
        av_packet_free(&pkt);
        return;
    }

    if(audioPlayer && videoPlayer && audio_stream_index >= 0)
    {
        //当存在音频流时，则以音频流做音视频同步
        videoPlayer->synpts = audioPlayer->pts;
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(1));
    
}
